package cate.game.event;

import cate.game.common.scanner.GameScanner;
import cate.game.common.scanner.IScanner;
import easy.java.practice.errorcode.EcException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

@Slf4j
public class GameEventLoader<E extends GameEvent> implements IScanner {

    protected Map<Class<E>, List<GameEventListener<E>>> eventListeners;

    public GameEventLoader() {
        eventListeners = new HashMap<>();
        GameScanner.registScanner(this);
    }

    @SuppressWarnings("unchecked")
    private static <E> Class<E> trans2Class(Type type, int index) {
        if (type instanceof Class<?> && GameEvent.class.isAssignableFrom((Class<?>) type)) {
            return (Class<E>) type;
        } else if (type instanceof ParameterizedType) {
            ParameterizedType pt = (ParameterizedType) type;
            Type t = pt.getActualTypeArguments()[index];
            return trans2Class(t, index);
        } else {
            String className = type == null ? "null" : type.getClass().getName();
            throw new IllegalArgumentException("Expected a Class, ParameterizedType"
                    + ", but <" + type + "> is of type " + className);
        }
    }

    private void registe(Class<?> clazz) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        for (Type type : clazz.getGenericInterfaces()) {
            Class<E> eventClass = trans2Class(type, 0);
            registe(eventClass, ((Class<GameEventListener<E>>) clazz).getConstructor().newInstance());
        }
    }

    private void registe(Class<E> clazz, GameEventListener<E> instance) {
        eventListeners.computeIfAbsent(clazz, k -> new ArrayList<>());
        eventListeners.get(clazz).add(instance);
    }

    public void registe(GameEventListener<GameEvent> listener) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
        registe(listener.getClass());
    }

    public void dispatchEvent(E event) {
        List<GameEventListener<E>> listeners = eventListeners.get(event.getClass());
        if (!CollectionUtils.isEmpty(listeners)) {
            listeners.forEach(listener -> dispatchEvent(event, listener));
        }
    }

    protected void dispatchEvent(E event, GameEventListener<E> listener) {
        try {
            listener.onEvent(event);
        } catch (Exception t) {
            if (listener.isFailOnException()) {
                throw new EcException("Exception while executing event-listener");
            } else {
                log.error("Exception while executing event-listener, which was ignored", t);
            }
        }
    }

    @Override
    public boolean match(Class<?> clazz) {
        return clazz.getAnnotation(NotInUse.class) == null && GameEventListener.class.isAssignableFrom(clazz) && !clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers());
    }

    @Override
    public void handle(Class<?> clazz) {
        for (Type type : clazz.getGenericInterfaces()) {
            Class<E> eventClass = trans2Class(type, 0);
            eventListeners.computeIfAbsent(eventClass, k -> new ArrayList<>());
            try {
                eventListeners.get(eventClass).add(((Class<GameEventListener<E>>) clazz).getConstructor().newInstance());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void afterScan() {
        eventListeners.forEach((k, v) -> v.sort((o1, o2) -> {
            if (o1.getOrder() > o2.getOrder()) {
                return 1;
            }
            if (o1.getOrder() < o2.getOrder()) {
                return -1;
            }
            return o1.hashCode() - o2.hashCode();
        }));
    }
}
